__all__ = ['parse_command_line', 'log_config', 'init', 'schedule_tasks', 'clean']


import argparse
import datetime
import logging
import sys
import types

import util


__author__  = 'ZL <837806295@qq.com>'
__product__ = 'searchit'
__version__ = '1.1.0'
__date__    = '2023/05/11'


log = logging.getLogger(__name__)
log.addHandler(logging.NullHandler())  # 如果宿主未配置日志处理器，则丢弃日志

args = types.SimpleNamespace()

password = None


def build_nfa_from_keys(keys):
    """从关键字构建非确定型有穷自动机

    0表示初始状态，其它状态用大于0的整数表示。表示状态的整数是连续的。接受状态集用整数的集合
    表示

    转移函数由数组刻划，数组下标是状态，对应的值是一个字典。字典的键是输入符号，字典的值是要跳
    转到的状态。对于初始状态，字典的值是状态集合。对于可接受状态，是空字典。对于其他状态，字典
    的值是单个状态值
    """

    if not isinstance(keys, list) or not keys:
        log.critical('build nfa from keys ... failed ... exit | illegal input')
        sys.exit(1)

    statistics = {}
    deltas = []
    acceptance = set()
    acceptance_to_key = {}

    deltas.append({})
    state = 0

    for key in keys:
        length = len(key)
        statistics[key] = set()

        for j in range(length):
            deltas.append({})
            state += 1

            if j == 0:
                delta = deltas[0]
                if key[j] in delta.keys():
                    delta[key[j]].add(state)
                else:
                    delta[key[j]] = {state}
            else:
                delta = deltas[state - 1]
                delta[key[j]] = state

            if j == length - 1:
                acceptance.add(state)
                acceptance_to_key[state] = key

    automaton = types.SimpleNamespace()
    automaton.states = {0}
    automaton.progress = 0
    automaton.statistics = statistics
    automaton.deltas = deltas
    automaton.acceptance = acceptance
    automaton.acceptance_to_key = acceptance_to_key

    log.debug('build nfa from keys ... successed | automaton: %s', automaton)
    return automaton

def run_nfa_for_searching(automaton, text):
    """运行自动机搜索文本

    会记录进度、当前状态和统计结果到状态机中，因此如果不重置状态机，则多次调用这个接口也会被当
    成是一次搜索
    """

    if (not isinstance(automaton, types.SimpleNamespace)
            or not isinstance(text, str)
            or not text):
        log.critical('run nfa for searching ... failed ... exit | illegal '
                     'input')
        sys.exit(1)

    statistics = automaton.statistics
    deltas = automaton.deltas
    acceptance = automaton.acceptance
    acceptance_to_key = automaton.acceptance_to_key

    for c in text:
        automaton.progress += 1

        states = set()
        for state in automaton.states:
            if state == 0:
                states.add(state)

                delta = deltas[state]
                if c in delta.keys():
                    next = delta[c]

                    for state2 in next:
                        if state2 in acceptance:
                            key = acceptance_to_key[state2]
                            statistics[key].add(automaton.progress)
                        else:
                            states.add(state2)
            else:
                delta = deltas[state]
                if c in delta.keys():
                    next = delta[c]

                    if next in acceptance:
                        key = acceptance_to_key[next]
                        statistics[key].add(automaton.progress)
                    else:
                        states.add(next)

        automaton.states = states

    log.debug('run nfa for searching ... successed | automaton: %s', automaton)
    return automaton

def reset_nfa(automaton):
    """重置状态机搜索状态

    重置后，再搜索文本，会被认为是一次新的搜索
    """

    if not isinstance(automaton, types.SimpleNamespace):
        log.critical('reset nfa ... failed ... exit | illegal input')
        sys.exit(1)

    automaton.states = {0}
    automaton.progress = 0
    automaton.statistics = {
        k: set() for k in automaton.acceptance_to_key.values()
    }

    log.debug('reset nfa ... successed | automaton: %s', automaton)
    return automaton

def get_nfa_statistics(automaton):
    return automaton.statistics

def get_nfa_progress(automaton):
    return automaton.progress

def prompt_statistics(progress, statistics):
    """输出统计的规范提示信息
    """

    if (not isinstance(progress, int)
            or not isinstance(statistics, dict)
            or not statistics):
        log.critical('prompt statistics ... failed ... exit | illegal input')
        sys.exit(1)

    now = datetime.datetime.now()

    prompt = '[%s]Search %d:' % (now.strftime('%Y-%m-%d %H:%M:%S'), progress)
    print(prompt)
    log.info('prompt statistics ... ongoing | total: %d', progress)

    for k, v in statistics.items():
        prompt = '%s -> %d' % (k, len(v))
        print(prompt)
        log.info('prompt statistics ... ongoing | %s', prompt)

def search():
    log.info('search ... ongoing')

    util.prompt_status('Search', 'ongoing')

    nfa = build_nfa_from_keys(args.keys)

    try:
        with open(args.path, 'r', encoding='utf-8') as f:
            while True:
                str2 = f.read(args.mb_per_read * 1024 * 1024)
                if str2:
                    run_nfa_for_searching(nfa, str2)
                else:
                    break
    except Exception:
        log.exception('search ... exception ... exit')
        sys.exit(1)

    prompt_statistics(get_nfa_progress(nfa), get_nfa_statistics(nfa))

    log.info('search ... done')

def parse_command_line():
    parser = argparse.ArgumentParser(
        prog=__product__,
        description='用非确定型有穷自动机构建的一个多关键字同时搜索的小工具')

    parser.add_argument('-v', '--version',
                        action='version',
                        version='%(prog)s ' + __version__)

    subparsers = parser.add_subparsers(
        title='subcommands',
        required=True,
        dest='subcommand',
        description='子命令集，由相应的子命令完成相应的功能')

    common = argparse.ArgumentParser(add_help=False)
    common.add_argument('-m', '--mb_per_read',
                        type=util.arg_type_mb_per_read,
                        default=4,
                        help='每次操作读文件数据的大小，单位是M，最小为1，最大为1024，'
                             '默认是4')

    # search子命令，从命令行获取搜索关键字，搜索文本文档
    search = subparsers.add_parser(
        'search',
        help='从命令行获取搜索关键字，搜索文本文档',
        parents=[
            util.arg_log(),
            util.arg_log_identifier(),
            util.arg_shutdown(),
            common
        ]
    )
    search.add_argument('path',
                        type=util.arg_type_file,
                        help='待搜索文本文档的完整路径')
    search.add_argument('keys',
                        type=util.arg_type_non_empty2,
                        nargs='+',
                        help='用来搜索的关键字集')

    global args
    args = parser.parse_args()

def log_config():
    if hasattr(args, 'log'):
        if hasattr(args, 'log_identifier') and args.log_identifier:
            return (
                '%s_v%s' % (__product__, __version__),
                args.log,
                args.log_identifier
            )
        else:
            return ('%s_v%s' % (__product__, __version__), args.log)

    return None

def init():
    log.info('program version: %s %s', __product__, __version__)
    log.debug('command line: %s', sys.argv)
    log.debug('parse args: %s', args)

    # 获取操作系统密码以便任务完成后自动关机
    if hasattr(args, 'shutdown'):
        global password
        password = util.get_os_password(args.shutdown)

def schedule_tasks():
    if args.subcommand == 'search':
        search()

def clean():
    # 自动关机
    if hasattr(args, 'shutdown'):
        util.shutdown(password)
